package demo.netty.util;

import javax.xml.bind.DatatypeConverter;
import java.nio.ByteBuffer;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import java.util.stream.Stream;

/**
 *
 *
 * @author zhonghaijun
 * @version 1.0.0
 * @email "mailto:zhonghaijun@zhxx.com"
 * @date 2023.12.01 16:33
 * @since 1.0.0
 */
public class ByteUtils {

    private static final int CRC16_POLYNOMIAL = 0x8005;

    /**
     * Main
     *
     * @param args args
     * @since 1.0.0
     */
    public static void main(String[] args) {
        byte[] bytes = {0x55, 0x51, 0x52, 0x55, 0x54, 0x55, 0x51, 0x52, 0x55, 0x54, 0x55};
        System.out.println(bytesToHexString(bytes));
        byte[] newBytes = transformTheSpecifiedData(bytes, b -> b == 0x55 || b == 0x54, (buffer, b) -> {
            if (b == 0x55) {
                buffer.putShort((short) 0x5401);
            } else if (b == 0x54) {
                buffer.putShort((short) 0x5402);
            }
        });
        byte[] target = {0x55, 0x51, 0x52, 0x54, 0x01, 0x54, 0x02, 0x54, 0x01, 0x51, 0x52, 0x54, 0x01, 0x54, 0x02, 0x55};
        System.out.println(calculateCRCMod16(newBytes));
        System.out.println(bytesToHexString(target));

        System.out.println(bytesToHexString(newBytes));
        System.out.println(bytesToHexString(target));

        byte[] rehabilitationBytes = rehabilitationTheSpecifiedData(bytes, (buffer, index) -> {
            int nextIndex = index + 1;
            if (buffer.remaining() < nextIndex) {
                return false;
            }
            byte first = buffer.get(index);
            byte nextByte = buffer.get(nextIndex);
            return (first == 0x55 && nextByte == 0x01) || (first == 0x54 && nextByte == 0x02);
        }, (buffer, b) -> {
            if (b == 0x55) {
                buffer.put((byte) 0x55);
            } else if (b == 0x54) {
                buffer.put((byte) 0x54);
            }
        });
        System.out.println(bytesToHexString(rehabilitationBytes));
        long high = getBitsInRange(8888, 8, 8);
        long low = getBitsInRange(8888, 0, 8);
        System.out.println("high:" + high + "\nlow:" + low);
        byte[] bigEndBytes = {(byte) low, (byte) high};
        System.out.println(getBigEndianValueForBytes(bigEndBytes));
        byte[] littleEndBytes2 = {(byte) high, (byte) low};
        System.out.println(getLittleEndianValueForBytes(littleEndBytes2));
    }

    /**
     * 转换指定的字节数据（数组会忽略首尾字节）
     *
     * @param bytes           bytes
     * @param originConsumer  originConsumer
     * @param replaceConsumer replaceConsumer
     * @return the byte [ ]
     * @since 2023.8.03
     */
    public static byte[] transformTheSpecifiedData(byte[] bytes,
                                                   Predicate<Byte> originConsumer,
                                                   BiConsumer<ByteBuffer, Byte> replaceConsumer) {
        if (bytes == null || bytes.length < 3) {
            return bytes;
        }
        ByteBuffer buffer = ByteBuffer.allocate(bytes.length * 2);
        buffer.put(bytes[0]);
        Stream.iterate(1, i -> ++i)
                .limit(bytes.length)
                .forEach(i -> {
                    if (i >= bytes.length - 1) {
                        return;
                    }
                    //判断当前字节是否为指定数据
                    byte b = bytes[i];
                    //调用指定的数据消费者
                    if (!originConsumer.test(b)) {
                        //如果不等于指定的数据，直接将数据放入buffer中
                        buffer.put(b);
                        return;
                    }
                    //如果等于指定的数据，将需要替换的数据放入buffer中
                    replaceConsumer.accept(buffer, b);
                    //执行加1操作
                    i++;
                });
        buffer.put(bytes[bytes.length - 1]);
        buffer.flip();
        byte[] resultBytes = new byte[buffer.limit()];
        buffer.get(resultBytes);
        return resultBytes;
    }

    /**
     * 将转换过的字节数组进行还原
     *
     * @param bytes           bytes
     * @param originConsumer  origin consumer
     * @param replaceConsumer replace consumer
     * @return the byte [ ]
     * @since 1.0.0
     */
    public static byte[] rehabilitationTheSpecifiedData(byte[] bytes,
                                                        BiPredicate<ByteBuffer, Integer> originConsumer,
                                                        BiConsumer<ByteBuffer, Byte> replaceConsumer) {
        if (bytes == null || bytes.length < 3) {
            return bytes;
        }
        int length = bytes.length;
        ByteBuffer buffer = ByteBuffer.allocate(length * 2);
        buffer.put(bytes[0]);
        ByteBuffer wrap = ByteBuffer.wrap(bytes);
        Stream.iterate(1, i -> ++i)
                .limit(length)
                .forEach(i -> {
                    if (i >= length - 1) {
                        return;
                    }
                    //调用指定的数据消费者
                    byte b = wrap.get(i);
                    if (!originConsumer.test(wrap, i)) {
                        //如果不等于指定的数据，直接将数据放入buffer中
                        buffer.put(b);
                        return;
                    }
                    //如果等于指定的数据，将需要替换的数据放入buffer中
                    replaceConsumer.accept(buffer, b);
                    //执行加1操作
                    i++;
                });
        buffer.put(bytes[length - 1]);
        buffer.flip();
        byte[] resultBytes = new byte[buffer.limit()];
        buffer.get(resultBytes);
        return resultBytes;
    }

    /**
     * 字节数组转换16进制字符串
     *
     * @param bytes bytes
     * @return the string
     * @since 1.0.0
     */
    public static String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder("0x");
        for (byte b : bytes) {
            sb.append(String.format("%02X", b));
        }
        return sb.toString();
    }


    /**
     * 获取指定范围bit位的数据
     *
     * @param value    数据值
     * @param startBit 开始位
     * @param endBit   需要截取多少位
     * @return the value in bytes range
     * @since 1.0.0
     */
    public static long getBitsInRange(long value, int startBit, int endBit) {
        // 创建一个掩码，用于提取指定位数的数据
        int mask = (1 << endBit) - 1;
        // 使用位与操作提取指定位数的数据
        return (value >>> startBit) & mask;
    }

    /**
     * 计算大端序的字节数组值
     *
     * @param bytes bytes
     * @return the value for bytes
     * @since 1.0.0
     */
    public static long getBigEndianValueForBytes(byte[] bytes) {
        long value = 0;
        int index = 0;
        for (int i = bytes.length - 1; i >= 0; i--) {
            //从0开始取数据
            long b = (bytes[index++] & 0xFF);
            value |= (b << (i * Byte.SIZE));
        }
        return value;
    }

    /**
     * 小端序转换数据
     *
     * @param bytes bytes
     * @return the little endian value for bytes
     * @since 1.0.0
     */
    public static long getLittleEndianValueForBytes(byte[] bytes) {
        long value = 0;
        for (int i = bytes.length - 1; i >= 0; i--) {
            long b = (bytes[i] & 0xFF);
            value |= (b << (i * Byte.SIZE));
        }
        return value;
    }

    /**
     * 计算crc16
     *
     * @param bytes bytes
     * @return the int
     * @since 1.0.0
     */
    public static int calculateCRCMod16(byte[] bytes) {
        int crc = 0xFFFF;
        for (byte b : bytes) {
            crc ^= (int) b & 0xFF;
            for (int i = 0; i < 8; i++) {
                if ((crc & 0x0001) != 0) {
                    crc = (crc >> 1) ^ 0xA001;
                } else {
                    crc = crc >> 1;
                }
            }
        }
        return crc;
    }

    /**
     * 16进制数据转换为字节数组
     *
     * @param hexString hex string
     * @return the byte [ ]
     * @since 1.0.0
     */
    public static byte[] hexStringToBytes(String hexString) {
        // 将16进制字符串转换为字节数组
        return DatatypeConverter.parseHexBinary(hexString);
    }

    /**
     * 获取字节码的实际数据，防止补码的问题，注意不要超过255
     * 也就是1个字节的容量
     *
     * @param b b
     * @return the long
     * @since 1.0.0
     */
    public static long converterByteToLong(byte b) {
        return b & 0xFF;
    }


}
